package spider

import (
	"asocrawler/conf"
	"asocrawler/db"
	"asocrawler/tool"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (

	/* APP */
	// 1免费榜 0付费榜 2畅销榜
	IosAppRank = "https://api.qimai.cn/rank/index"

	IosAppRankHtmlUrl = "https://www.qimai.cn/rank/index/brand/%s/device/iphone/country/cn/genre/%s/date/%s"
)

type IosSpider struct {
	ChannelName  string
	TimeInterval int
	Platform     string
	AppIconMap   map[string]int
}

func NewIosDriver() (v *IosSpider) {
	v = &IosSpider{
		ChannelName: "IOS",
		Platform:    "IOS",
		AppIconMap:  make(map[string]int),
	}
	return
}

func (this IosSpider) GetWorkerInfo() (w tool.WorkerInfo) {
	w.ChannelName = this.ChannelName
	return w
}

func (this IosSpider) Run() {
	go func() {
		this.run()
	}()
}
func (this *IosSpider) Println(v ...interface{}) {
	var str = fmt.Sprintf("[%s]-[%s]", this.ChannelName, this.Platform)
	if conf.Conf.Debug.Switch {
		str += "-[Debug]"
	}
	log.Println(str, fmt.Sprintln(v...))
}

func (this *IosSpider) iosAppDataRequest(page int, brand, date, analysisP, genreP string) (p db.IosAppResult, err error) {

	client := &http.Client{}

	data := url.Values{}
	data.Add("analysis", analysisP)
	data.Add("genre", genreP)
	data.Add("brand", brand)
	data.Add("device", "iphone")
	data.Add("country", "cn")
	data.Add("page", fmt.Sprint(page))
	data.Add("date", date)

	req, err := http.NewRequest("GET", IosAppRank+"?"+data.Encode(), nil)
	if err != nil {
		return
	}
	req.Header.Set("referer", tool.QiMai_Referer)
	req.Header.Set("Origin", tool.QiMai_Origin)
	req.Header.Set("User-Agent", tool.QiMai_UserAgent)
	req.Header.Set("cookie", tool.QiMai_Cookie)

	resp, err := client.Do(req)
	if err != nil {
		time.Sleep(1 * time.Second)
		return
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	err = json.Unmarshal(b, &p)
	if err != nil {
		this.Println("request:", IosAppRank+"?"+data.Encode())
		return
	}
	if p.Code != 10000 {
		err = errors.New("p.Code != 0,p=" + string(b))
		return
	}

	return
}

func (this *IosSpider) collectIosAppData(date, brandId string) (rs []db.DbDataResult, err error) {
	paramsMap := make(map[string]db.Params)
	paramsMap["全部游戏"] = db.Params{
		G: "6014",
	}
	paramsMap["策略"] = db.Params{
		G: "7017",
	}
	paramsMap["动作"] = db.Params{
		G: "7001",
	}
	paramsMap["家庭聚会"] = db.Params{
		G: "7009",
	}
	paramsMap["角色扮演"] = db.Params{
		G: "7014",
	}
	paramsMap["竞速"] = db.Params{
		G: "7013",
	}
	paramsMap["卡牌"] = db.Params{
		G: "7005",
	}
	paramsMap["冒险"] = db.Params{
		G: "7002",
	}
	paramsMap["模拟"] = db.Params{
		G: "7015",
	}
	paramsMap["体育"] = db.Params{
		G: "7016",
	}
	paramsMap["问答"] = db.Params{
		G: "7018",
	}
	paramsMap["休闲"] = db.Params{
		G: "7003",
	}
	paramsMap["益智解谜"] = db.Params{
		G: "7012",
	}
	paramsMap["音乐"] = db.Params{
		G: "7011",
	}
	paramsMap["桌面"] = db.Params{
		G: "7004",
	}
	paramsMap["字谜"] = db.Params{
		G: "7019",
	}
	paramsMap["娱乐场游戏"] = db.Params{
		G: "7006",
	}

	for rankType, pm := range paramsMap {
		intervalTime := tool.GetRandomMillSecondNum()
		time.Sleep(intervalTime * time.Millisecond)

		rs1, err := this.packData(brandId, date, rankType, pm)
		if err != nil {
			log.Println("collectIosAppData error,err=", err)
			return rs, err
		}
		rs = append(rs, rs1)

		time.Sleep(intervalTime * time.Millisecond)
	}
	return

}

func (this *IosSpider) packData(brandId, date, rankType string, pm db.Params) (vs db.DbDataResult, err error) {

	var brand string
	switch brandId {
	case "0":
		brand = "paid"
		break
	case "1":
		brand = "free"
		break
	case "2":
		brand = "grossing"
	}

	vs.Date = date
	vs.ChannelName = this.ChannelName
	vs.Platform = this.Platform
	vs.RankType = rankType
	vs.Classify = brandId

	var rankInfo []db.IosAppResultInfo

	if conf.Conf.QiMaiCollectMode == "api" {
		// 接口获取

		rankInfo, err = this.collectDataByApi(pm, brand, date)
		if err != nil {
			this.Println(rankType+":"+"this.collectDataByApi error:", err)
			return
		}
	} else {
		// html解析

		rankInfo, err = this.collectDataByHtml(pm, brand, date)
		if err != nil {
			this.Println(rankType+":"+"this.collectDataByHtml error:", err)
			return
		}
	}

	for _, r := range rankInfo {

		fIndex, flag := r.Index.(float64)
		if !flag {
			dIndexStr, flag2 := r.Index.(string)
			if !flag2 {
				return vs, errors.New(fmt.Sprintf("r.Index 类型错误，非int或string,r.Index=%s", r.Index))
			}
			fIndex, err = strconv.ParseFloat(dIndexStr, 64)
			if err != nil {
				this.Println("r.Index string转float64失败", r.Index)
				fIndex = 1
			}

		}
		dIndex := int(fIndex)

		appId, flag := r.AppInfo.AppId.(string)
		if !flag {
			appIdInt, flag2 := r.AppInfo.AppId.(float64)
			if !flag2 {
				this.Println("packData error,appId 解析失败,data = ", pm.A)
				return vs, errors.New("packData error,appId 解析失败")
			}
			appId = fmt.Sprint(int(appIdInt))
		}

		if this.AppIconMap[appId] == 0 {
			//如果icon表中没有图片，则去下载
			err := db.GetAppIcon(r.AppInfo.Icon, appId, this.ChannelName, this.Platform)
			if err != nil {
				this.Println("getAppIcon error,err=", err, r.AppInfo.Icon)
			}
			//标记 已存 防止同一id 多次存入icon表
			this.AppIconMap[appId] = 1
		}

		// 获取应用详情
		var detailInfo db.ApiData
		var v db.ApiData
		if conf.Conf.QiMaiCollectMode == "api" {
			// 接口获取详情
			time.Sleep(200 * time.Millisecond)

			detailInfo, err = this.collectAppDetail(appId)
			if err != nil {
				if err.Error() == "ip被禁用" {
					this.Println("collectAppDetail ip被禁用，退出")
					return vs, err
				}
				this.Println("collectAppDetail error,err =", err)
				continue
			}

			// 组装数据
			v = db.ApiData{
				AppId:       appId,
				GameName:    r.AppInfo.AppName,
				GameVersion: "",
				PlayCount:   0,
				DIndex:      dIndex - 1, //从0开始
				BRank:       1,
				Company:     r.AppInfo.Publisher,
				Classify:    brandId,

				TypeName:           detailInfo.TypeName,
				Score:              detailInfo.Score,
				DownloadNum:        detailInfo.DownloadNum,
				OnSaleDate:         detailInfo.OnSaleDate,
				GameDesc:           detailInfo.GameDesc,
				TotalDownloadCount: 0,
				CommentCount:       detailInfo.CommentCount,
				OriginalUrl:        detailInfo.OriginalUrl,
			}
		} else {
			// todo html解析详情

			commentCount, _ := strconv.Atoi(r.AppInfo.AppCommentCount)

			// 组装数据
			v = db.ApiData{
				AppId:       appId,
				GameName:    r.AppInfo.AppName,
				GameVersion: "",
				PlayCount:   0,
				DIndex:      dIndex - 1, //从0开始
				BRank:       1,
				Company:     r.AppInfo.Publisher,
				Classify:    brandId,

				CommentCount: commentCount,
				Score:        fmt.Sprint(r.AppInfo.AppCommentScore),

				// TypeName:           detailInfo.TypeName,
				// Score:              detailInfo.Score,
				// DownloadNum:        detailInfo.DownloadNum,
				// OnSaleDate:         detailInfo.OnSaleDate,
				// GameDesc:           detailInfo.GameDesc,
				// TotalDownloadCount: 0,
				// CommentCount:       detailInfo.CommentCount,
				// OriginalUrl:        detailInfo.OriginalUrl,
			}
		}

		vs.Data = append(vs.Data, v)

	}

	return
}

// 模式1 从接口中获取数据
func (this *IosSpider) collectDataByApi(pm db.Params, brand, date string) (rs []db.IosAppResultInfo, err error) {

	tryTimes := 0
	page := 1

re:
	// 生成 analysis 参数 注意page变化 要放在re中
	aParams := fmt.Sprintf(`{"brand":"%s","genre":%s,"device":"iphone","country":"cn","page":%d,"date":"%s"}`, brand, pm.G, page, date)
	pm.A, err = tool.GetAnalysisParams(IosAppRank, aParams)
	if err != nil {
		this.Println("getAnalysisParams error,err=", err)
		return
	}

	// 请求
	p, err := this.iosAppDataRequest(page, brand, date, pm.A, pm.G)
	if err != nil {
		if p.BanIp != "" {
			err = errors.New("ip被禁用,退出")
			return
		} else {
			if tryTimes < 20 {
				intervalTime := tool.GetRandomMillSecondNum()
				this.Println(" error,iosAppDataRequest 请求失败,重新请求中", err)
				tryTimes++
				time.Sleep(intervalTime * time.Millisecond)
				goto re
			}
			err = errors.New("iosAppDataRequest 多次请求失败,退出")
			return
		}
	}

	// 统一50条
	rs = append(rs, p.RankInfo...)

	//  不足100条 继续分页查询
	// if len(rs) < 100 {
	// 	page++
	// 	goto re
	// }

	return
}

// 模式2 从html中解析
func (this *IosSpider) collectDataByHtml(pm db.Params, barnd, date string) (rs []db.IosAppResultInfo, err error) {

	var pUrl = fmt.Sprintf(IosAppRankHtmlUrl, barnd, pm.G, date)
	rs, err = tool.GetQiMaiDataByIosHtml(pUrl)
	if err != nil {
		this.Println("解析七麦HTML文本失败，err=", err)
	}

	return

}

func (this *IosSpider) collectAppDetail(appId string) (res db.ApiData, err error) {
	var tryTimes = 0
re:
	aParams := fmt.Sprintf(`{"appid":%s,"country":"cn"}`, appId)
	analysisP, err := tool.GetAnalysisParams(tool.QiMai_Ios_Detail_Api, aParams)
	if err != nil {
		return
	}

	client := &http.Client{}

	data := url.Values{}

	data.Add("analysis", analysisP)
	data.Add("country", "cn")
	data.Add("appid", appId)

	req, err := http.NewRequest("GET", tool.QiMai_Ios_Detail_Api+"?"+data.Encode(), nil)
	if err != nil {
		return
	}

	req.Header.Set("referer", tool.QiMai_Referer)
	req.Header.Set("Origin", tool.QiMai_Origin)
	req.Header.Set("User-Agent", tool.QiMai_UserAgent)
	req.Header.Set("cookie", tool.QiMai_Cookie)

	resp, err := client.Do(req)
	if err != nil {
		time.Sleep(1 * time.Second)
		return
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	var p db.IosAppDetailResult
	err = json.Unmarshal(b, &p)
	if err != nil {
		this.Println("json.Unmarshal error,尝试重新请求,string(b)=", string(b), tool.QiMai_Ios_Detail_Api+"?"+data.Encode())
		if tryTimes < 20 {
			tryTimes++
			goto re
		}
		this.Println("json.Unmarshal 多次请求失败，退出")
		return
	}
	if p.Code != 10000 {
		if p.BanIp != "" {
			return res, errors.New("ip被禁用" + p.BanIp)
		}
		if tryTimes < 20 {
			this.Println("code != 10000,尝试重新请求", tool.QiMai_Ios_Detail_Api+"?"+data.Encode())
			tryTimes++
			goto re
		}
		this.Println("code != 10000 多次请求失败，退出")
		err = errors.New("p.Code != 0,p=" + string(b))
		return
	}

	appIdInt, err := strconv.Atoi(appId)
	if err != nil {
		return
	}

	// 从详情接口获取的数据
	res.OriginalUrl = fmt.Sprintf(tool.QiMai_Ios_Detail, appIdInt)
	res.OnSaleDate = p.AppInfo.ReleaseTime
	res.CommentCount = p.AppInfo.RateDetail.All.RatingCount
	res.DownloadNum = p.AppInfo.YesterdayDownload
	res.GameDesc = p.AppInfo.Subtitle
	res.Score = p.AppInfo.CurrentVersionUserRating
	res.TypeName = p.AppInfo.Genre.Name
	return
}

func (this *IosSpider) collectApp(date, brandId string) (bool, error) {
	rankTypeList, err := db.QueryRankTypeList(this.ChannelName, this.Platform)
	if err != nil {
		this.Println("collectApp QueryRankTypeList err:", err)
		return false, err
	}

	rs, err := this.collectIosAppData(date, brandId)
	if err != nil {
		return false, err
	}
	for _, r := range rs {
		//获取排行榜ID和表后缀
		r.TablePostfix = rankTypeList[r.RankType].TablePostfix
		r.RankTypeId = rankTypeList[r.RankType].Id

		if len(r.Data) == 0 {
			return false, errors.New("collectApp r 长度为空")
		} else {
			err = db.SaveData(r)
			if err != nil {
				return false, err
			}
		}
	}
	return true, nil
}

func (this *IosSpider) Collect(date string) (bol bool, err error) {
	this.AppIconMap, err = db.GetAppIconMap(this.ChannelName, this.Platform)
	if err != nil {
		this.Println("db.GetAppIconMap error,err=", err)
		return false, err
	}

	// 0 付费榜
	bol, err = this.collectApp(date, "0")
	if err != nil {
		this.Println("collectApp0 err = ", err)
		return bol, err
	}
	this.Println("付费榜数据收集完成")

	// 1 免费榜
	bol, err = this.collectApp(date, "1")
	if err != nil {
		this.Println("collectApp1 err = ", err)
		return bol, err
	}
	this.Println("免费榜数据收集完成")

	// 2 畅销榜
	bol, err = this.collectApp(date, "2")
	if err != nil {
		this.Println("collectApp2 err = ", err)
		return bol, err
	}
	this.Println("畅销榜数据收集完成")

	return true, nil

}

func (this *IosSpider) run() {
	if conf.Conf.Debug.Switch {
		tool.Worker.Post(this)
	} else {
		//启动时执行一次
		tool.Worker.Post(this)
		for {
			hour := time.Now().Hour()
			if hour == 7 || hour == 21 {
				tool.Worker.Post(this)
			}
			time.Sleep(1 * 60 * time.Minute)
		}
	}
}
